/*
 * Copyright (C) 2018 - 2019, Jue Huang <rabbit@pear.hk>
 *
 * Crypto API
 * 1. sign
 * 2. verify
 * 3. new key
 */

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdbool.h>
#include <openssl/ec.h>
#include <openssl/sha.h>
#include <openssl/ecdsa.h>
#include <openssl/ripemd.h>
#include <openssl/obj_mac.h>

//#include <openssl/ripemd.h>

#include "pear_key.h"
#include "log/pear_log.h"
#include "utils/pear_dump.h"

static pr_pubkey_t *pr_get_pubkey(EC_KEY *key, pr_pubkey_t *pubkey) 
{
    unsigned int   size = 0;
    unsigned char *keyp = NULL;

    size = i2o_ECPublicKey(key, NULL);

    keyp = (unsigned char *)pubkey;

	if (size == 0)
    {
	    PEAR_LOG("i2o_ECPublicKey failed\n");
        return NULL;
    }

    if (i2o_ECPublicKey(key, &keyp) != size)
    {
        PEAR_LOG("i2o_ECPublicKey returned unexpected size\n");
        return NULL;
    }

	return pubkey;
}

static EC_KEY *pr_set_pubkey(pr_pubkey_t *pubkey)
{
    EC_KEY *key = NULL;
    unsigned char *keyp = NULL;

    key = EC_KEY_new_by_curve_name(NID_secp256k1);

    keyp = (unsigned char *)pubkey;

    if (o2i_ECPublicKey(&key, (const unsigned char **)&keyp, sizeof(pr_pubkey_t)) == NULL)
    {
        return NULL;
    }

    return key;
}

static pr_privkey_t *pr_get_privkey(EC_KEY *key, pr_privkey_t *privkey) 
{
    unsigned int   size = 0;
    unsigned char *keyp = NULL;

    keyp = (unsigned char *)privkey;

    size = i2d_ECPrivateKey(key, NULL);
    if (!size)
    {
        PEAR_LOG("CKey::GetPrivKey() : i2d_ECPrivateKey failed\n");
        return NULL;
    }

    if (i2d_ECPrivateKey(key, &keyp) != size)
    {
        PEAR_LOG("PrivKey size is %d and content is %s \n", size, keyp);
        return NULL;
    }

    return privkey;
}

static EC_KEY *pr_set_privkey(pr_privkey_t *privkey)
{
    EC_KEY *key = NULL;
    unsigned char *keyp = NULL;

/* memory ? 
 * key  = EC_KEY_new_by_curve_name(NID_secp256k1);
 */

    keyp = (unsigned char *)privkey;

    if (d2i_ECPrivateKey(&key, (const unsigned char **)&keyp, PRIVATE_KEY_SIZE) == NULL)
    {
        PEAR_LOG("pr_set_privkey error\n");
        return NULL;
    }

    return key;
}

static EC_KEY *pr_new_EC_KEY()
{
    EC_KEY* ec_key = NULL;

    ec_key = EC_KEY_new_by_curve_name(NID_secp256k1);
    if (ec_key == NULL)
    {
        PEAR_LOG("EC_KEY_new_by_curve_name failed\r\n");
        return NULL;
    }

    if (EC_KEY_generate_key(ec_key) == 0)
    {
        PEAR_LOG("Generate key error \r\n");
        EC_KEY_free(ec_key);
        return NULL;
    }

    return ec_key;
}

pr_key_t *pr_new_key()
{
    pr_key_t *key    = NULL;
    EC_KEY   *ec_key = NULL;
    
    key = (pr_key_t *) malloc(sizeof(pr_key_t));
    if (key == NULL)
    {
        PEAR_LOG("malloc pr_key_t Error\n");
        return NULL;
    }

    ec_key = pr_new_EC_KEY();
    if (ec_key == NULL)
    {
        free(key);
        return NULL;
    }

    if (pr_get_pubkey(ec_key, &(key->pubkey)) == NULL)
    {
        free(key);
        EC_KEY_free(ec_key);
        return NULL;
    }

    if (pr_get_privkey(ec_key, &(key->privkey)) == NULL)
    {
        free(key);
        EC_KEY_free(ec_key);
        return NULL;
    }

    EC_KEY_free(ec_key);

    return key;
}


int static inline ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig, const unsigned char *msg, int msglen, int recid, int check)
{
    if (!eckey) return 0;

    int ret = 0;
    BN_CTX *ctx = NULL;

    BIGNUM *x = NULL;
    BIGNUM *e = NULL;
    BIGNUM *order = NULL;
    BIGNUM *sor = NULL;
    BIGNUM *eor = NULL;
    BIGNUM *field = NULL;
    EC_POINT *R = NULL;
    EC_POINT *O = NULL;
    EC_POINT *Q = NULL;
    BIGNUM *rr = NULL;
    BIGNUM *zero = NULL;
    int n = 0;
    int i = recid / 2;

    const EC_GROUP *group = EC_KEY_get0_group(eckey);
    if ((ctx = BN_CTX_new()) == NULL) { ret = -1; goto err; }
    BN_CTX_start(ctx);
    order = BN_CTX_get(ctx);
    if (!EC_GROUP_get_order(group, order, ctx)) { ret = -2; goto err; }
    x = BN_CTX_get(ctx);
    if (!BN_copy(x, order)) { ret=-1; goto err; }
    if (!BN_mul_word(x, i)) { ret=-1; goto err; }
    if (!BN_add(x, x, ecsig->r)) { ret=-1; goto err; }
    field = BN_CTX_get(ctx);
    if (!EC_GROUP_get_curve_GFp(group, field, NULL, NULL, ctx)) { ret=-2; goto err; }
    if (BN_cmp(x, field) >= 0) { ret=0; goto err; }
    if ((R = EC_POINT_new(group)) == NULL) { ret = -2; goto err; }
    if (!EC_POINT_set_compressed_coordinates_GFp(group, R, x, recid % 2, ctx)) { ret=0; goto err; }
    if (check)
    {
        if ((O = EC_POINT_new(group)) == NULL) { ret = -2; goto err; }
        if (!EC_POINT_mul(group, O, NULL, R, order, ctx)) { ret=-2; goto err; }
        if (!EC_POINT_is_at_infinity(group, O)) { ret = 0; goto err; }
    }
    if ((Q = EC_POINT_new(group)) == NULL) { ret = -2; goto err; }
    n = EC_GROUP_get_degree(group);
    e = BN_CTX_get(ctx);
    if (!BN_bin2bn(msg, msglen, e)) { ret=-1; goto err; }
    if (8*msglen > n) BN_rshift(e, e, 8-(n & 7));
    zero = BN_CTX_get(ctx);
    if (!BN_zero(zero)) { ret=-1; goto err; }
    if (!BN_mod_sub(e, zero, e, order, ctx)) { ret=-1; goto err; }
    rr = BN_CTX_get(ctx);
    if (!BN_mod_inverse(rr, ecsig->r, order, ctx)) { ret=-1; goto err; }
    sor = BN_CTX_get(ctx);
    if (!BN_mod_mul(sor, ecsig->s, rr, order, ctx)) { ret=-1; goto err; }
    eor = BN_CTX_get(ctx);
    if (!BN_mod_mul(eor, e, rr, order, ctx)) { ret=-1; goto err; }
    if (!EC_POINT_mul(group, Q, eor, R, sor, ctx)) { ret=-2; goto err; }
    if (!EC_KEY_set_public_key(eckey, Q)) { ret=-2; goto err; }

    ret = 1;

err:
    if (ctx) {
        BN_CTX_end(ctx);
        BN_CTX_free(ctx);
    }
    if (R != NULL) EC_POINT_free(R);
    if (O != NULL) EC_POINT_free(O);
    if (Q != NULL) EC_POINT_free(Q);
    return ret;
}


/*
 * Create a compact signature (65 bytes)
 * Which allows reconstructing the used public key
 */
bool pear_signCompact(char * hash, int hashsize, char * vchSig ,EC_KEY* pkey)
{
	bool fOk = false;
	ECDSA_SIG *sig = ECDSA_do_sign(hash, hashsize, pkey);
        printf("pear_signCompact %p\r\n",sig);
	if (sig==NULL)
	    return false;

 //   memset(vchSig, 0x00, 64);
	int nBitsR = BN_num_bits(sig->r);
	int nBitsS = BN_num_bits(sig->s);
        printf("nBitsR: %d and nBitsS: %d! \r\n",nBitsR,nBitsS);
	if (nBitsR <= 256 && nBitsS <= 256)
	{
	    vchSig[0] = 0+27;
	    BN_bn2bin(sig->r,&vchSig[33-(nBitsR+7)/8]);
	    BN_bn2bin(sig->s,&vchSig[65-(nBitsS+7)/8]);
	    fOk = true;
	}
	ECDSA_SIG_free(sig);
	return fOk;
}



/*
 * Reconstruct public key from a compact signature && Verify
 */
bool pear_SetCompactSignature(char* hash, int hashsize, const char* vchSig,int sigsize,EC_KEY **pkey)
{
        int rec = 0;
        if (sigsize != 65)
            return false;
        if (vchSig[0]<27 || vchSig[0]>=31)
            return false;

        rec = (vchSig[0] - 27) & ~4;
        if (rec < 0 || rec >= 3)
        {
            PEAR_LOG("\n\n rec error\n\n");
        }
        ECDSA_SIG *sig = ECDSA_SIG_new();
        BN_bin2bn(&vchSig[1],32,sig->r);
        BN_bin2bn(&vchSig[33],32,sig->s);

        EC_KEY_free(*pkey);
        *pkey = EC_KEY_new_by_curve_name(NID_secp256k1);
        if (ECDSA_SIG_recover_key_GFp(*pkey, sig, hash, hashsize, rec, 0) == 1)
        {
            ECDSA_SIG_free(sig);
            return true;
        }
        return false;
}


pr_sign_t *pr_sign(char *data, int data_len, pr_sign_t *sign, pr_privkey_t *privkey)
{
    EC_KEY  *key = NULL;

    if ((key = pr_set_privkey(privkey)) == NULL)
    {
        PEAR_LOG("set privkey errno: %d\n", errno);
        return NULL;
    }

    if (ECDSA_sign(0, data, data_len, (unsigned char *)sign->data, &(sign->size), key) == 0)
    {
        PEAR_LOG("set privkey errno: %d\n", errno);
        EC_KEY_free(key);
        return NULL;
    }

    EC_KEY_free(key);

    return sign;
}

pr_compact_sign_t *pr_compact_sign(char *data, int data_len, pr_compact_sign_t *sign, pr_privkey_t *privkey)
{
    EC_KEY  *key = NULL;

    if ((key = pr_set_privkey(privkey)) == NULL)
    {
        PEAR_LOG("set privkey errno: %d\n", errno);
        return NULL;
    }

    if (pear_signCompact(data, data_len, (unsigned char *)sign->data, key) == false)
    {
        PEAR_LOG("set privkey errno: %d\n", errno);
        EC_KEY_free(key);
        return NULL;
    }

    sign->size = 65;

    EC_KEY_free(key);

    return sign;
}

int pr_verify(char *data, int data_len, pr_sign_t *sign, pr_pubkey_t *pubkey)
{
    EC_KEY *key  = NULL;

    if ((key = pr_set_pubkey(pubkey)) == NULL)
    {
        PEAR_LOG("set pubkey error: %d\n", errno);
        return -1;
    }

    if (ECDSA_verify(0, data, data_len, (unsigned char *)sign->data, sign->size, key) != 1)
    {
        PEAR_LOG("set ECDSA_verify errno: %d\n", errno);
        EC_KEY_free(key);
        return -1;
    }

    EC_KEY_free(key);

    return 0;
}

int pr_compact_verify(char *data, int data_len, pr_compact_sign_t *sign, pr_pubkey_t *pubkey)
{
    EC_KEY *key  = NULL;

    if ((key = pr_set_pubkey(pubkey)) == NULL)
    {
        PEAR_LOG("set pubkey error: %d\n", errno);
        return -1;
    }

    if (pear_SetCompactSignature(data, data_len, (unsigned char *)sign->data, sign->size, &key) == false)
    {
        PEAR_LOG("SetCompactSignature verify errno: %d\n", errno);
        EC_KEY_free(key);
        return -1;
    }

    pr_pubkey_t pkey;
    if (pr_get_pubkey(key, &pkey) == NULL)
    {
        PEAR_LOG("ECDSA_verify recover_pubkey error\n");
        EC_KEY_free(key);
        return -1;
    }

    char buf[1024] = {0x00};
    PEAR_LOG("pubkey: %s\n", pr_hex_encode(buf, (unsigned char *)pubkey, sizeof(pr_pubkey_t)));
    PEAR_LOG("recover pubkey: %s\n", pr_hex_encode(buf, (unsigned char *)pkey, sizeof(pr_pubkey_t)));

    if (ECDSA_verify(0, data, data_len, (unsigned char *)sign->data, sign->size, key) != 1)
    {
        PEAR_LOG("set ECDSA_verify errno: %d\n", errno);
        EC_KEY_free(key);
        return -1;
    }

    EC_KEY_free(key);

    return 0;
}


/*
 * 1. sha256 32 Bytes
 * 2. ripemd160 32 to 20 Bytes
 *
 */
void pr_hash160(const char *src, int size, unsigned char *hash_160)
{
    unsigned char hash_256[32] = {0x00};

    SHA256(src, size, hash_256);

    RIPEMD160((unsigned char*)&hash_256, 32, (unsigned char*)hash_160);
}


void pr_hash256(const char *src, int size, unsigned char *hash_256)
{
    SHA256(src, size, hash_256);
}







